﻿using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Template.Infrastructure.Entity;
using Template.Infrastructure.Enums;
using Template.Infrastructure.Filter;


namespace Template.Infrastructure.Extensions
{
    /// <summary>
    /// Queryable扩展
    /// </summary>
    public static partial class Extensions
    {

        /// <summary>
        /// 扩展Between 操作符
        /// </summary>
        /// <typeparam name="TSource">动态实体</typeparam>
        /// <typeparam name="TKey">键</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="keySelector">键选择器</param>
        /// <param name="low">低</param>
        /// <param name="high">高</param>
        /// <returns></returns>
        public static IQueryable<TSource> Between<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector,
            TKey low, TKey high) where TKey : IComparable<TKey>
        {

            Expression<Func<TSource, bool>> lambda = Expression.Lambda<Func<TSource, bool>>(source.GenerateBetween(keySelector, low, high), keySelector.Parameters);
            return source.Where(lambda);
        }


        /// <summary>
        /// 扩展NotBetween操作符
        /// </summary>
        /// <typeparam name="TSource">动态实体</typeparam>
        /// <typeparam name="TKey">键</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="keySelector">键选择器</param>
        /// <param name="low">低</param>
        /// <param name="high">高</param>
        /// <returns></returns>
        public static IQueryable<TSource> NotBetween<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector,
            TKey low, TKey high) where TKey : IComparable<TKey>
        {
            //取反
            var not = Expression.Not(source.GenerateBetween(keySelector, low, high));
            Expression<Func<TSource, bool>> lambda = Expression.Lambda<Func<TSource, bool>>(not, keySelector.Parameters);
            return source.Where(lambda);
        }
        /// <summary>
        /// 生成Between操作符
        /// </summary>
        /// <typeparam name="TSource">动态实体</typeparam>
        /// <typeparam name="TKey">键</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="keySelector">键选择器</param>
        /// <param name="low">低</param>
        /// <param name="high">高</param>
        /// <returns></returns>

        private static Expression GenerateBetween<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector,
            TKey low, TKey high) where TKey : IComparable<TKey>
        {
            source.NotNull(nameof(source));
            keySelector.NotNull(nameof(keySelector));
            low.NotNull(nameof(low));
            high.NotNull(nameof(high));
            Expression key = Expression.Invoke(keySelector, keySelector.Parameters);
            Expression lowerBound = Expression.GreaterThanOrEqual(key, Expression.Constant(low)); //18
            Expression upperBound = Expression.LessThanOrEqual(key, Expression.Constant(high)); //21
            Expression and = Expression.AndAlso(lowerBound, upperBound);
            return and;
        }


        /// <summary>
        /// 开始日期、截止日期。
        /// </summary>
        /// <typeparam name="T">动态对象</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="getter"></param>
        /// <param name="from">来自</param>
        /// <param name="to">到</param>
        /// <returns></returns>
        public static IQueryable<T> DateRange<T>(this IQueryable<T> source, Expression<Func<T, DateTime>> getter, DateTime from, DateTime to)
        {
            source.NotNull(nameof(source));
            getter.NotNull(nameof(getter));

            Expression body = getter.Body;
            var greaterExp = Expression.GreaterThanOrEqual(body, Expression.Constant(from));
            var lessExp = Expression.LessThanOrEqual(body, Expression.Constant(to));
            var and = Expression.And(greaterExp, lessExp);
            var lambda = Expression.Lambda<Func<T, bool>>(and, getter.Parameters);
            return source.Where(lambda);
        }


        /// <summary>
        ///  从指定<see cref="IQueryable{TSource}"/>集合中查询条件过滤
        /// </summary>
        /// <typeparam name="TSource">动态实体类型</typeparam>
        /// <param name="source">要查询的数据集</param>
        /// <param name="predicate">查询条件谓语表达式</param>
        /// <param name="condition">条件过滤</param>
        /// <returns>返回已过滤的查询集合</returns>
        public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, bool condition)
        {
            source.NotNull(nameof(source));
            predicate.NotNull(nameof(predicate));

            return condition ? source.Where(predicate) : source;
        }

        /// <summary>
        /// 从集合中查询指定数据筛选的分页信息
        /// </summary>
        /// <typeparam name="TEntity">动态实体类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="predicate">查询条件表达式</param>
        /// <param name="pageParameters">分页参数</param>
        /// <returns></returns>
        public static async Task<PageResult<TEntity>> ToPageAsync<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, bool>> predicate, PageParameters pageParameters)
            where TEntity: IEntity
        {
            pageParameters.NotNull(nameof(pageParameters));
      
            try
            {
                var result = await source.WhereAsync(pageParameters.PageIndex, pageParameters.PageSize, predicate, pageParameters.OrderConditions);
                var list = await result.Data.ToArrayAsync();
                var total = result.TotalNumber;
                return new PageResult<TEntity>(list, total);
            }
            catch (Exception ex)
            {

                throw;
            }
 
        }

        /// <summary>
        /// 从集合中查询指定数据筛选的分页信息
        /// </summary>
        /// <typeparam name="TEntity">动态实体类型</typeparam>
        /// <typeparam name="TResult">要返回动态实体类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="predicate">查询条件表达式</param>
        /// <param name="pageParameters">分页参数</param>
        /// <param name="selector">数据筛选表达式</param>
        /// <returns></returns>
        public static async Task<PageResult<TResult>> ToPageAsync<TEntity, TResult>(this IQueryable<TEntity> source, Expression<Func<TEntity, bool>> predicate, PageParameters pageParameters, Expression<Func<TEntity, TResult>> selector)
             where TEntity : IEntity
        {
            pageParameters.NotNull(nameof(pageParameters));
            selector.NotNull(nameof(selector));
            var result = await source.WhereAsync(pageParameters.PageIndex, pageParameters.PageSize, predicate, pageParameters.OrderConditions);
            var list =await result.Data.Select(selector).ToArrayAsync();
            var total = result.TotalNumber;
            return new PageResult<TResult>(list, total);
        }



        /// <summary>
        /// 从集合中查询指定输出DTO的分页信息
        /// </summary>
        /// <typeparam name="TEntity">动态实体类型</typeparam>
        /// <typeparam name="TOutputDto">输出DTO数据类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="predicate">查询条件表达式</param>
        /// <param name="pageParameters">分页参数</param>
        /// <returns></returns>
        public static async Task<PageResult<TOutputDto>> ToPageAsync<TEntity, TOutputDto>(this IQueryable<TEntity> source, Expression<Func<TEntity, bool>> predicate, PageParameters pageParameters)
             where TEntity : IEntity
          where TOutputDto : IOutputDto
        {
            pageParameters.NotNull(nameof(pageParameters));
            var result = await source.WhereAsync(pageParameters.PageIndex, pageParameters.PageSize, predicate, pageParameters.OrderConditions);
            var list = await result.Data.ToOutput<TOutputDto>().ToArrayAsync();
            var total = result.TotalNumber;
            return new PageResult<TOutputDto>(list, total);
        }










        /// <summary>
        ///  从指定集合中查询指定分页条件的数据集 
        /// </summary>
        /// <typeparam name="TEntity">动态实体类型</typeparam>
        /// <param name="source">要查询的数据集</param>
        /// <param name="pageIndex">分页索引</param>
        /// <param name="pageSize">分页大小</param>
        /// <param name="predicate">查询条件表达式</param>
        /// <param name="orderConditions">排序条件集合</param>
        /// <returns></returns>

        private static async Task<(IQueryable<TEntity> Data, int TotalNumber)> WhereAsync<TEntity>(this IQueryable<TEntity> source, int pageIndex,
              int pageSize, Expression<Func<TEntity, bool>> predicate, OrderCondition[] orderConditions)
        {

            try
            {

                int total = !predicate.IsNull() ? await source.CountAsync(predicate) : await source.CountAsync();
                if (!predicate.IsNull())
                {
                    source = source.Where(predicate);
                }
             
                IOrderedQueryable<TEntity> orderSource = null;
                if (orderConditions == null || orderConditions.Length == 0)
                {
                    orderSource = CollectionPropertySorter<TEntity>.OrderBy(source, "Id", SortDirection.Ascending);
                }

                int count = 0;

                foreach (OrderCondition orderCondition in orderConditions)
                {
                    orderSource = count == 0
                        ? CollectionPropertySorter<TEntity>.OrderBy(source, orderCondition.SortField, orderCondition.SortDirection)
                        : CollectionPropertySorter<TEntity>.ThenBy(orderSource, orderCondition.SortField, orderCondition.SortDirection);
                    count++;
                }

                source = orderSource;

                return (!source.IsNull() ? source.Skip(pageSize * (pageIndex - 1)).Take(pageSize) : Enumerable.Empty<TEntity>().AsQueryable(), total);

            }
            catch (Exception ex)
            {
                throw ex;

            }
        }
    }
}
